<?php
namespace Org\OAuth2;

define("OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME", 3600);
define("OAUTH2_DEFAULT_AUTH_CODE_LIFETIME", 30);
define("OAUTH2_DEFAULT_REFRESH_TOKEN_LIFETIME", 1209600);
define("OAUTH2_CLIENT_ID_REGEXP", "/^[a-z0-9-_]{3,32}$/i");
define("OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN", "token");
define("OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE", "code");
define("OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN", "code-and-token");
define("OAUTH2_AUTH_RESPONSE_TYPE_REGEXP", "/^(token|code|code-and-token)$/");
define("OAUTH2_GRANT_TYPE_AUTH_CODE", "authorization_code");
define("OAUTH2_GRANT_TYPE_USER_CREDENTIALS", "password");
define("OAUTH2_GRANT_TYPE_ASSERTION", "assertion");
define("OAUTH2_GRANT_TYPE_REFRESH_TOKEN", "refresh_token");
define("OAUTH2_GRANT_TYPE_NONE", "none");
define("OAUTH2_GRANT_TYPE_REGEXP", "/^(authorization_code|password|assertion|refresh_token|none)$/");
define("OAUTH2_TOKEN_PARAM_NAME", "oauth_token");
define("OAUTH2_HTTP_FOUND", "302 Found");
define("OAUTH2_HTTP_BAD_REQUEST", "400 Bad Request");
define("OAUTH2_HTTP_UNAUTHORIZED", "401 Unauthorized");
define("OAUTH2_HTTP_FORBIDDEN", "403 Forbidden");
define("OAUTH2_ERROR_INVALID_REQUEST", "invalid_request");
define("OAUTH2_ERROR_INVALID_CLIENT", "invalid_client");
define("OAUTH2_ERROR_UNAUTHORIZED_CLIENT", "unauthorized_client");
define("OAUTH2_ERROR_REDIRECT_URI_MISMATCH", "redirect_uri_mismatch");
define("OAUTH2_ERROR_USER_DENIED", "access_denied");
define("OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE", "unsupported_response_type");
define("OAUTH2_ERROR_INVALID_SCOPE", "invalid_scope");
define("OAUTH2_ERROR_INVALID_GRANT", "invalid_grant");
define("OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE", "unsupported_grant_type");
define("OAUTH2_ERROR_INVALID_TOKEN", "invalid_token");
define("OAUTH2_ERROR_EXPIRED_TOKEN", "expired_token");
define("OAUTH2_ERROR_INSUFFICIENT_SCOPE", "insufficient_scope");


define("PDO_DSN", "mysql:dbname=oauth2server;host=localhost");
define("PDO_USER", "root");
define("PDO_PASS", "root");

class OAuth2 extends AbsOAuth2 {

	private $db;

	public function __construct() {
		parent::__construct();

		try {
			$this->db = new \PDO(PDO_DSN, PDO_USER, PDO_PASS);
		} catch (PDOException $e) {
			die('Connection failed: ' . $e->getMessage());
		}
	}

	function __destruct() {
		$this->db = NULL; // Release db connection
	}

	private function handleException($e) {
		echo "Database error: " . $e->getMessage();
		exit;
	}

	public function addClient($client_id, $client_secret, $redirect_uri) {
		try {
			$sql = "INSERT INTO clients (client_id, client_secret, redirect_uri) VALUES (:client_id, :client_secret, :redirect_uri)";
			$stmt = $this->db->prepare($sql);
			$stmt->bindParam(":client_id", $client_id, \PDO::PARAM_STR);
			$stmt->bindParam(":client_secret", $client_secret, \PDO::PARAM_STR);
			$stmt->bindParam(":redirect_uri", $redirect_uri, \PDO::PARAM_STR);
			$stmt->execute();
		} catch (PDOException $e) {
			$this->handleException($e);
		}
	}

	protected function checkClientCredentials($client_id, $client_secret = NULL) {
		try {
			$sql = "SELECT client_secret FROM clients WHERE client_id = :client_id";
			$stmt = $this->db->prepare($sql);
			$stmt->bindParam(":client_id", $client_id, \PDO::PARAM_STR);
			$stmt->execute();

			$result = $stmt->fetch(\PDO::FETCH_ASSOC);

			if ($client_secret === NULL)
				return false;

			return $result["client_secret"] == $client_secret;
		} catch (PDOException $e) {
			$this->handleException($e);
		}
	}

	protected function getRedirectUri($client_id) {
		try {
			$sql = "SELECT redirect_uri FROM clients WHERE client_id = :client_id";
			$stmt = $this->db->prepare($sql);
			$stmt->bindParam(":client_id", $client_id, \PDO::PARAM_STR);
			$stmt->execute();

			$result = $stmt->fetch(\PDO::FETCH_ASSOC);

			if ($result === FALSE)
				return FALSE;

			return isset($result["redirect_uri"]) && $result["redirect_uri"] ? $result["redirect_uri"] : NULL;
		} catch (PDOException $e) {
			$this->handleException($e);
		}
	}

	protected function getAccessToken($oauth_token) {
		try {
			$sql = "SELECT client_id, expires, scope FROM tokens WHERE oauth_token = :oauth_token";
			$stmt = $this->db->prepare($sql);
			$stmt->bindParam(":oauth_token", $oauth_token, \PDO::PARAM_STR);
			$stmt->execute();

			$result = $stmt->fetch(\PDO::FETCH_ASSOC);

			return $result !== FALSE ? $result : NULL;
		} catch (PDOException $e) {
			$this->handleException($e);
		}
	}

	protected function setAccessToken($oauth_token, $client_id, $expires, $scope = NULL) {
		try {
			$sql = "INSERT INTO tokens (oauth_token, client_id, expires, scope) VALUES (:oauth_token, :client_id, :expires, :scope)";
			$stmt = $this->db->prepare($sql);
			$stmt->bindParam(":oauth_token", $oauth_token, \PDO::PARAM_STR);
			$stmt->bindParam(":client_id", $client_id, \PDO::PARAM_STR);
			$stmt->bindParam(":expires", $expires, \PDO::PARAM_INT);
			$stmt->bindParam(":scope", $scope, \PDO::PARAM_STR);

			$stmt->execute();
		} catch (PDOException $e) {
			$this->handleException($e);
		}
	}

	protected function getSupportedGrantTypes() {
		return array(
				OAUTH2_GRANT_TYPE_AUTH_CODE,
		);
	}

	protected function getAuthCode($code) {
		try {
			$sql = "SELECT code, client_id, redirect_uri, expires, scope FROM auth_codes WHERE code = :code";
			$stmt = $this->db->prepare($sql);
			$stmt->bindParam(":code", $code, \PDO::PARAM_STR);
			$stmt->execute();

			$result = $stmt->fetch(\PDO::FETCH_ASSOC);

			return $result !== FALSE ? $result : NULL;
		} catch (PDOException $e) {
			$this->handleException($e);
		}
	}

	protected function setAuthCode($code, $client_id, $redirect_uri, $expires, $scope = NULL) {
		try {
			$sql = "INSERT INTO auth_codes (code, client_id, redirect_uri, expires, scope) VALUES (:code, :client_id, :redirect_uri, :expires, :scope)";
			$stmt = $this->db->prepare($sql);
			$stmt->bindParam(":code", $code, \PDO::PARAM_STR);
			$stmt->bindParam(":client_id", $client_id, \PDO::PARAM_STR);
			$stmt->bindParam(":redirect_uri", $redirect_uri, \PDO::PARAM_STR);
			$stmt->bindParam(":expires", $expires, \PDO::PARAM_INT);
			$stmt->bindParam(":scope", $scope, \PDO::PARAM_STR);

			$stmt->execute();
		} catch (PDOException $e) {
			$this->handleException($e);
		}
	}

	/*验证token是否有效*/
	public function verifyToken($clientId,$token){
		$thedate = strtotime(date("Y-m-d H:i:s"));
		$sql	= "select * from tokens where client_id = $clientId and oauth_token = '$token' and expires > $thedate";
		$select	= $this->db->prepare($sql);
		$select->execute();
		 
		$result 	= $select->fetch(\PDO::FETCH_ASSOC);
		 
		if($result == NULL)
			return false;
		else
			return true;
		 
	}
	
	/*验证ClientId和Secret*/
	public function verifyClient($clientId,$secret){
		$select	= $this->db->prepare("select * from clients where client_id = '$clientId'");
		$select->execute();
		
		$result	= $select->fetch(\PDO::FETCH_ASSOC);
		
		if($result == NULL || $secret != md5($result['client_secret']))
			return false;
		return true;
	}
}

abstract class AbsOAuth2 {

	protected $conf = array();

	public function getVariable($name, $default = NULL) {
		return isset($this->conf[$name]) ? $this->conf[$name] : $default;
	}

	public function setVariable($name, $value) {
		$this->conf[$name] = $value;
		return $this;
	}

	abstract protected function checkClientCredentials($client_id, $client_secret = NULL);

	abstract protected function getRedirectUri($client_id);

	abstract protected function getAccessToken($oauth_token);

	abstract protected function setAccessToken($oauth_token, $client_id, $expires, $scope = NULL);

	protected function getSupportedGrantTypes() {
		return array();
	}

	protected function getSupportedAuthResponseTypes() {
		return array(
				OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE,
				OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN,
				OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN
		);
	}

	protected function getSupportedScopes() {
		return array();
	}

	protected function checkRestrictedAuthResponseType($client_id, $response_type) {
		return TRUE;
	}

	protected function checkRestrictedGrantType($client_id, $grant_type) {
		return TRUE;
	}

	protected function getAuthCode($code) {
		return NULL;
	}

	protected function setAuthCode($code, $client_id, $redirect_uri, $expires, $scope = NULL) {
	}

	protected function checkUserCredentials($client_id, $username, $password) {
		return FALSE;
	}

	protected function checkAssertion($client_id, $assertion_type, $assertion) {
		return FALSE;
	}

	protected function getRefreshToken($refresh_token) {
		return NULL;
	}

	protected function setRefreshToken($refresh_token, $client_id, $expires, $scope = NULL) {
		return;
	}

	protected function unsetRefreshToken($refresh_token) {
		return;
	}

	protected function checkNoneAccess($client_id) {
		return FALSE;
	}

	protected function getDefaultAuthenticationRealm() {
		return "Service";
	}

	public function __construct($config = array()) {
		foreach ($config as $name => $value) {
			$this->setVariable($name, $value);
		}
	}

	public function verifyAccessToken($scope = NULL, $exit_not_present = TRUE, $exit_invalid = TRUE, $exit_expired = TRUE, $exit_scope = TRUE, $realm = NULL) {
		$token_param = $this->getAccessTokenParams();
		if ($token_param === FALSE) // Access token was not provided
			return $exit_not_present ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_BAD_REQUEST, $realm, OAUTH2_ERROR_INVALID_REQUEST, 'The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.', NULL, $scope) : FALSE;
		// Get the stored token data (from the implementing subclass)
		$token = $this->getAccessToken($token_param);
		if ($token === NULL)
			return $exit_invalid ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_INVALID_TOKEN, 'The access token provided is invalid.', NULL, $scope) : FALSE;

		// Check token expiration (I'm leaving this check separated, later we'll fill in better error messages)
		if (isset($token["expires"]) && time() > $token["expires"])
			return $exit_expired ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_EXPIRED_TOKEN, 'The access token provided has expired.', NULL, $scope) : FALSE;

		// Check scope, if provided
		// If token doesn't have a scope, it's NULL/empty, or it's insufficient, then throw an error
		if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->checkScope($scope, $token["scope"])))
			return $exit_scope ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_FORBIDDEN, $realm, OAUTH2_ERROR_INSUFFICIENT_SCOPE, 'The request requires higher privileges than provided by the access token.', NULL, $scope) : FALSE;

		return TRUE;
	}

	private function checkScope($required_scope, $available_scope) {
		// The required scope should match or be a subset of the available scope
		if (!is_array($required_scope))
			$required_scope = explode(" ", $required_scope);

		if (!is_array($available_scope))
			$available_scope = explode(" ", $available_scope);

		return (count(array_diff($required_scope, $available_scope)) == 0);
	}

	private function getAccessTokenParams() {
		$auth_header = $this->getAuthorizationHeader();

		if ($auth_header !== FALSE) {
			// Make sure only the auth header is set
			if (isset($_GET[OAUTH2_TOKEN_PARAM_NAME]) || isset($_POST[OAUTH2_TOKEN_PARAM_NAME]))
				$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Auth token found in GET or POST when token present in header');

			$auth_header = trim($auth_header);

			// Make sure it's Token authorization
			if (strcmp(substr($auth_header, 0, 5), "OAuth ") !== 0)
				$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Auth header found that doesn\'t start with "OAuth"');

			// Parse the rest of the header
			if (preg_match('/\s*OAuth\s*="(.+)"/', substr($auth_header, 5), $matches) == 0 || count($matches) < 2)
				$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Malformed auth header');

			return $matches[1];
		}

		if (isset($_GET[OAUTH2_TOKEN_PARAM_NAME])) {
			if (isset($_POST[OAUTH2_TOKEN_PARAM_NAME])) // Both GET and POST are not allowed
				$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Only send the token in GET or POST, not both');

			return $_GET[OAUTH2_TOKEN_PARAM_NAME];
		}

		if (isset($_POST[OAUTH2_TOKEN_PARAM_NAME]))
			return $_POST[OAUTH2_TOKEN_PARAM_NAME];

		return FALSE;
	}

	public function grantAccessToken() {
		$filters = array(
				"grant_type" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => OAUTH2_GRANT_TYPE_REGEXP), "flags" => FILTER_REQUIRE_SCALAR),
				"scope" => array("flags" => FILTER_REQUIRE_SCALAR),
				"code" => array("flags" => FILTER_REQUIRE_SCALAR),
				"redirect_uri" => array("filter" => FILTER_SANITIZE_URL),
				"username" => array("flags" => FILTER_REQUIRE_SCALAR),
				"password" => array("flags" => FILTER_REQUIRE_SCALAR),
				"assertion_type" => array("flags" => FILTER_REQUIRE_SCALAR),
				"assertion" => array("flags" => FILTER_REQUIRE_SCALAR),
				"refresh_token" => array("flags" => FILTER_REQUIRE_SCALAR),
		);

		$input = filter_input_array(INPUT_POST, $filters);

		// Grant Type must be specified.
		if (!$input["grant_type"])
			$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing');

		// Make sure we've implemented the requested grant type
		if (!in_array($input["grant_type"], $this->getSupportedGrantTypes()))
			$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE);

		// Authorize the client
		$client = $this->getClientCredentials();

		if ($this->checkClientCredentials($client[0], $client[1]) === FALSE)
			$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT);

		if (!$this->checkRestrictedGrantType($client[0], $input["grant_type"]))
			$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNAUTHORIZED_CLIENT);

		// Do the granting
		switch ($input["grant_type"]) {
			case OAUTH2_GRANT_TYPE_AUTH_CODE:
				if (!$input["code"] || !$input["redirect_uri"])
					$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST);

					$stored = $this->getAuthCode($input["code"]);

					// Ensure that the input uri starts with the stored uri
					if ($stored === NULL || (strcasecmp(substr($input["redirect_uri"], 0, strlen($stored["redirect_uri"])), $stored["redirect_uri"]) !== 0) || $client[0] != $stored["client_id"])
						$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT);

					if ($stored["expires"] < time())
						$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN);

					break;
			case OAUTH2_GRANT_TYPE_USER_CREDENTIALS:
				if (!$input["username"] || !$input["password"])
					$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Missing parameters. "username" and "password" required');

					$stored = $this->checkUserCredentials($client[0], $input["username"], $input["password"]);

					if ($stored === FALSE)
						$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT);

					break;
			case OAUTH2_GRANT_TYPE_ASSERTION:
				if (!$input["assertion_type"] || !$input["assertion"])
					$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST);

					$stored = $this->checkAssertion($client[0], $input["assertion_type"], $input["assertion"]);

					if ($stored === FALSE)
						$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT);

					break;
			case OAUTH2_GRANT_TYPE_REFRESH_TOKEN:
				if (!$input["refresh_token"])
					$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'No "refresh_token" parameter found');

					$stored = $this->getRefreshToken($input["refresh_token"]);

					if ($stored === NULL || $client[0] != $stored["client_id"])
						$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT);

					if ($stored["expires"] < time())
						$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN);

					// store the refresh token locally so we can delete it when a new refresh token is generated
					$this->setVariable('_old_refresh_token', $stored["token"]);

					break;
			case OAUTH2_GRANT_TYPE_NONE:
				$stored = $this->checkNoneAccess($client[0]);

				if ($stored === FALSE)
					$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST);
		}

		// Check scope, if provided
		if ($input["scope"] && (!is_array($stored) || !isset($stored["scope"]) || !$this->checkScope($input["scope"], $stored["scope"])))
			$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_SCOPE);

		if (!$input["scope"])
			$input["scope"] = NULL;

		$token = $this->createAccessToken($client[0], $input["scope"]);

		$this->sendJsonHeaders();
		echo json_encode($token);
	}

	protected function getClientCredentials() {
		if (isset($_SERVER["PHP_AUTH_USER"]) && $_POST && isset($_POST["client_id"]))
			$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT);

		// Try basic auth
		if (isset($_SERVER["PHP_AUTH_USER"]))
			return array($_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"]);

		// Try POST
		if ($_POST && isset($_POST["client_id"])) {
			if (isset($_POST["client_secret"]))
				return array($_POST["client_id"], $_POST["client_secret"]);

			return array($_POST["client_id"], NULL);
		}

		// No credentials were specified
		$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT);
	}

	public function getAuthorizeParams() {
		$filters = array(
				"client_id" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => OAUTH2_CLIENT_ID_REGEXP), "flags" => FILTER_REQUIRE_SCALAR),
				"response_type" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => OAUTH2_AUTH_RESPONSE_TYPE_REGEXP), "flags" => FILTER_REQUIRE_SCALAR),
				"redirect_uri" => array("filter" => FILTER_SANITIZE_URL),
				"state" => array("flags" => FILTER_REQUIRE_SCALAR),
				"scope" => array("flags" => FILTER_REQUIRE_SCALAR),
		);

		$input = filter_input_array(INPUT_GET, $filters);

		// Make sure a valid client id was supplied
		if (!$input["client_id"]) {
			if ($input["redirect_uri"])
				$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, NULL, NULL, $input["state"]);

			$this->errorJsonResponse(OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_CLIENT); // We don't have a good URI to use
		}

		// redirect_uri is not required if already established via other channels
		// check an existing redirect URI against the one supplied
		$redirect_uri = $this->getRedirectUri($input["client_id"]);

		// At least one of: existing redirect URI or input redirect URI must be specified
		if (!$redirect_uri && !$input["redirect_uri"])
			$this->errorJsonResponse(OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_REQUEST);

		// getRedirectUri() should return FALSE if the given client ID is invalid
		// this probably saves us from making a separate db call, and simplifies the method set
		if ($redirect_uri === FALSE)
			$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, NULL, NULL, $input["state"]);

		// If there's an existing uri and one from input, verify that they match
		if ($redirect_uri && $input["redirect_uri"]) {
			// Ensure that the input uri starts with the stored uri
			if (strcasecmp(substr($input["redirect_uri"], 0, strlen($redirect_uri)), $redirect_uri) !== 0)
				$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_REDIRECT_URI_MISMATCH, NULL, NULL, $input["state"]);
		}
		elseif ($redirect_uri) { // They did not provide a uri from input, so use the stored one
			$input["redirect_uri"] = $redirect_uri;
		}

		// type and client_id are required
		if (!$input["response_type"])
			$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_REQUEST, 'Invalid response type.', NULL, $input["state"]);

		// Check requested auth response type against the list of supported types
		if (array_search($input["response_type"], $this->getSupportedAuthResponseTypes()) === FALSE)
			$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE, NULL, NULL, $input["state"]);

		// Restrict clients to certain authorization response types
		if ($this->checkRestrictedAuthResponseType($input["client_id"], $input["response_type"]) === FALSE)
			$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNAUTHORIZED_CLIENT, NULL, NULL, $input["state"]);

		// Validate that the requested scope is supported
		if ($input["scope"] && !$this->checkScope($input["scope"], $this->getSupportedScopes()))
			$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_SCOPE, NULL, NULL, $input["state"]);

		return $input;
	}

	public function finishClientAuthorization($is_authorized, $params = array(), $selfParam = null) {
		$params += array(
				'scope' => NULL,
				'state' => NULL,
		);
		extract($params);

		if ($state !== NULL)
			$result["query"]["state"] = $state;

		if ($is_authorized === FALSE) {
			$result["query"]["error"] = OAUTH2_ERROR_USER_DENIED;
		}
		else {
			if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE || $response_type == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN)
				$result["query"]["code"] = $this->createAuthCode($client_id, $redirect_uri, $scope);

			if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN || $response_type == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN)
				$result["fragment"] = $this->createAccessToken($client_id, $scope);
		}

		if(is_array($selfParam) && $selfParam != null){
			foreach($selfParam as $key=>$value){
				$result["query"][$key]	= $value;
			}
		}

		$this->doRedirectUriCallback($redirect_uri, $result);
	}

	private function doRedirectUriCallback($redirect_uri, $params) {
		header("HTTP/1.1 ". OAUTH2_HTTP_FOUND);
		header("Location: " . $this->buildUri($redirect_uri, $params));
		exit;
	}

	private function buildUri($uri, $params) {
		$parse_url = parse_url($uri);

		// Add our params to the parsed uri
		foreach ($params as $k => $v) {
			if (isset($parse_url[$k]))
				$parse_url[$k] .= "&" . http_build_query($v);
			else
				$parse_url[$k] = http_build_query($v);
		}

		// Put humpty dumpty back together
		return
		((isset($parse_url["scheme"])) ? $parse_url["scheme"] . "://" : "")
		. ((isset($parse_url["user"])) ? $parse_url["user"] . ((isset($parse_url["pass"])) ? ":" . $parse_url["pass"] : "") . "@" : "")
		. ((isset($parse_url["host"])) ? $parse_url["host"] : "")
		. ((isset($parse_url["port"])) ? ":" . $parse_url["port"] : "")
		. ((isset($parse_url["path"])) ? $parse_url["path"] : "")
		. ((isset($parse_url["query"])) ? "?" . $parse_url["query"] : "")
		. ((isset($parse_url["fragment"])) ? "#" . $parse_url["fragment"] : "");
	}

	protected function createAccessToken($client_id, $scope = NULL) {
		$token = array(
				"access_token" => $this->genAccessToken(),
				"expires_in" => $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME),
				"scope" => $scope
		);

		$this->setAccessToken($token["access_token"], $client_id, time() + $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME), $scope);

		// Issue a refresh token also, if we support them
		if (in_array(OAUTH2_GRANT_TYPE_REFRESH_TOKEN, $this->getSupportedGrantTypes())) {
			$token["refresh_token"] = $this->genAccessToken();
			$this->setRefreshToken($token["refresh_token"], $client_id, time() + $this->getVariable('refresh_token_lifetime', OAUTH2_DEFAULT_REFRESH_TOKEN_LIFETIME), $scope);
			// If we've granted a new refresh token, expire the old one
			if ($this->getVariable('_old_refresh_token'))
				$this->unsetRefreshToken($this->getVariable('_old_refresh_token'));
		}

		return $token;
	}

	private function createAuthCode($client_id, $redirect_uri, $scope = NULL) {
		$code = $this->genAuthCode();
		$this->setAuthCode($code, $client_id, $redirect_uri, time() + $this->getVariable('auth_code_lifetime', OAUTH2_DEFAULT_AUTH_CODE_LIFETIME), $scope);
		return $code;
	}

	protected function genAccessToken() {
		return md5(base64_encode(pack('N6', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), uniqid())));
	}

	protected function genAuthCode() {
		return md5(base64_encode(pack('N6', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), uniqid())));
	}

	private function getAuthorizationHeader() {
		if (array_key_exists("HTTP_AUTHORIZATION", $_SERVER))
			return $_SERVER["HTTP_AUTHORIZATION"];

		if (function_exists("apache_request_headers")) {
			$headers = apache_request_headers();

			if (array_key_exists("Authorization", $headers))
				return $headers["Authorization"];
		}

		return FALSE;
	}

	private function sendJsonHeaders() {
		header("Content-Type: application/json");
		header("Cache-Control: no-store");
	}

	private function errorDoRedirectUriCallback($redirect_uri, $error, $error_description = NULL, $error_uri = NULL, $state = NULL) {
		$result["query"]["error"] = $error;

		if ($state)
			$result["query"]["state"] = $state;

		if ($this->getVariable('display_error') && $error_description)
			$result["query"]["error_description"] = $error_description;

		if ($this->getVariable('display_error') && $error_uri)
			$result["query"]["error_uri"] = $error_uri;

		$this->doRedirectUriCallback($redirect_uri, $result);
	}

	private function errorJsonResponse($http_status_code, $error, $error_description = NULL, $error_uri = NULL) {
		$result['error'] = $error;

		if ($this->getVariable('display_error') && $error_description)
			$result["error_description"] = $error_description;

		if ($this->getVariable('display_error') && $error_uri)
			$result["error_uri"] = $error_uri;

		header("HTTP/1.1 " . $http_status_code);
		$this->sendJsonHeaders();
		echo json_encode($result);

		exit;
	}

	private function errorWWWAuthenticateResponseHeader($http_status_code, $realm, $error, $error_description = NULL, $error_uri = NULL, $scope = NULL) {
		$realm = $realm === NULL ? $this->getDefaultAuthenticationRealm() : $realm;

		$result = "WWW-Authenticate: OAuth realm='" . $realm . "'";

		if ($error)
			$result .= ", error='" . $error . "'";

		if ($this->getVariable('display_error') && $error_description)
			$result .= ", error_description='" . $error_description . "'";

		if ($this->getVariable('display_error') && $error_uri)
			$result .= ", error_uri='" . $error_uri . "'";

		if ($scope)
			$result .= ", scope='" . $scope . "'";

		header("HTTP/1.1 ". $http_status_code);
		header($result);

		exit;
	}
}
